/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			openglShaderWork.d
	Description:	opengl shader interface
	Date:			22.12.2007 by Jurec
*/

module moonglide.renderer.opengl.OpenGLShaderWork;

import
	moonglide.io.LogSystem,
	moonglide.renderer.IRendererRes,
	moonglide.resources.resobjects.hilevel.ResShaderComposite,

	moonglide.renderer.opengl.OpenGLRenderer,
	moonglide.renderer.opengl.DefShaderProv,
	moonglide.renderer.opengl.OpenGLShaderARB,
	moonglide.renderer.opengl.OpenGLShaderGLSL;


enum : ubyte
{
	Shader_GLSL = 0,
	Shader_ARB = 1
}

//=============================================================
/// COpenGLShader - opengl renderer's shader support stuff
//=============================================================
class COpenGLShader : IShaderManager
{


	protected:
		COpenGLCaps				_caps;
		IShaderProvider			_defProv;
		IShaderProvider[ubyte]	_providers;

		IShaderProvider _GetProv( ubyte shaderType )
		{
			IShaderProvider res = _providers[ shaderType ];

			if( res is null )
				res = _defProv;

			return res;
		}

		SShaderLoc	_tempLoc;

		SShaderLoc[] _envFP;
		SShaderLoc[] _envVP;

		SShaderLoc	ligObjVec,
					ligObjEye,
					ligEye,
					normLig0Pos;


		void mainparse( inout CResShaderComposite sh )
		{
			/// detect shader type

				// NOTE (Jura#1#): maybe try to compile shader by all providers?\
				// if compile - shader type is detected!

			bool shARB = false;

			foreach( inout Pr; sh.progs )
				if( Pr )
				{
					if (Pr.text.length > 0)
						shARB =( containsPattern( Pr.text[0] , "!!ARBvp" ) || containsPattern( Pr.text[0] , "!!ARBfp" ) );
					sh.shaderType = ( shARB ) ? Shader_ARB : Shader_GLSL;

					Pr.define.sort;
					foreach( def; Pr.define )
					{
						debug debugLog.trace( def );
				//		Pr.id = Pr.id ~ "["~ def ~"]";
					}
					Pr.define.length = 0;
				}

			// CG detection..
		}

	public:
		void RegisterProvider( ubyte Styp, IShaderProvider ShProv )
		{
			_providers[Styp] = ShProv;
		}

		//=============================================================
		/// shader analyse stuff
		//=============================================================
		ubyte AnalyzeShader( inout CResShaderComposite sh )
		{
			mainparse( sh );
			return _GetProv( sh.shaderType ).Analyze( sh );
		}


		bool LoadShader( inout CResShaderComposite sh )
		{
			IShaderProvider Prov = _GetProv(sh.shaderType);
			return Prov.Upload(sh);
		}

		bool UnloadShader( inout CResShaderComposite sh )
		{
			IShaderProvider Prov = _GetProv( sh.shaderType );
			return Prov.Unload(sh);
		}

		bool LinkShader( inout CResShaderComposite sh )
		{
			IShaderProvider Prov = _GetProv(sh.shaderType);
			return Prov.Link(sh);
		}

		void BindShader( inout CResShaderComposite sh )
		{
			if( !sh.loaded )
				return;

			IShaderProvider Prov = _GetProv(sh.shaderType);
			Prov.Bind(sh);

			foreach( inout pr; sh.progs )
			if( pr )
				foreach( loc; pr.locals )
					if(( !loc.isStatic ) ||  (( loc.isStatic ) && ( !loc.binded )))
						if( sh.shaderType == Shader_GLSL )
							Prov.SetLocal( loc, sh.handle );
						else
							Prov.SetLocal( loc, pr.sType );
		}

		void UnBindShader( inout CResShaderComposite sh )
		{
			IShaderProvider Prov = _GetProv( sh.shaderType );
			Prov.Unbind( sh );
		}

		void BindEnv()
		{
			GetEnv( ligObjVec.num, sh_Vertex ).setData( COpenGLRenderer().getActiveObjLigthPos() );
			GetEnv( ligObjEye.num, sh_Vertex ).setData(  COpenGLRenderer().getActiveObjCameraPos() );
			GetEnv( ligEye.num, sh_Vertex ).setData( COpenGLRenderer().getActiveWorCameraPos() );

			Vector4f Vec = COpenGLRenderer().getActiveWorLightPos();
			Vec.normalize();

			GetEnv( normLig0Pos.num, sh_Fragment ).setData(  Vec );

			//debug debugLog.trace( layout( "ligObjVec: {} {} {}", ligObjVec.x, ligObjVec.y, ligObjVec.z ));
			//debug debugLog.trace( layout( "ligObjEye: {} {} {}", ligObjEye.x, ligObjEye.y, ligObjEye.z ));

			IShaderProvider pVP = _GetProv( sh_Vertex );
			pVP.Enable();

			if( _caps.useARB_vertex_prog )
				foreach( SShaderLoc loc; _envVP )
					if(( !loc.isStatic ) ||  (( loc.isStatic ) && ( !loc.binded )))
					{
						pVP.SetEnv(loc,sh_Vertex);
						loc.binded = true;
					}

			if( _caps.useARB_frag_prog )
				foreach( SShaderLoc loc; _envFP )
					if(( !loc.isStatic ) || (( loc.isStatic ) && ( !loc.binded )))
					{
						pVP.SetEnv(loc,sh_Fragment);
						loc.binded = true;
					}

			pVP.Disable();
		}


		//=============================================================
		/// This proc is for setting all-engine env`s. Env`s are not deletable, and adding in start
		/// of programm. (not isStatic ShaderLoc - updates every frame)
		//=============================================================
		void AddEnv( SShaderLoc lo, uint shaderType )
		{
			lo.binded = false;
			SShaderLoc* Slo = GetEnv( lo.num, shaderType );

			if( Slo !is null )
				(*Slo) = lo;

			if(( shaderType == sh_Vertex ) && ( _caps.useARB_vertex_prog ))
			{
				_envVP.length = _envVP.length + 1;
				_envVP[_envVP.length-1] = lo;
			}

			if ((shaderType == sh_Fragment)&&(_caps.useARB_frag_prog))
			{
				_envFP.length = _envFP.length + 1;
				_envFP[_envFP.length-1] = lo;
			}
		}


		SShaderLoc* GetEnv(uint num, uint shaderType)
		{
			if( shaderType == sh_Vertex )
				for( int i=0; i<_envVP.length; ++i)
					if (_envVP[i].num == num)
						return &_envVP[i];

			if( shaderType == sh_Fragment )
				for( int i=0; i < _envFP.length; ++i )
					if( _envFP[i].num == num )
						return &_envFP[i];

			return &_tempLoc;
		}


		//=============================================================
		/// constructor & destructor
		//=============================================================
		this( COpenGLCaps ca )
		{
			_tempLoc.name = "NotLoc";

			_defProv = new COpenGLDefShaderProvider();

			ca.useARB_vertex_prog	? RegisterProvider( Shader_ARB,  new COpenGLShaderARBProvider() )
				: RegisterProvider( Shader_ARB, null );

			ca.useGLSL 				? RegisterProvider( Shader_GLSL, new COpenGLShaderGLSLProvider() )
				: RegisterProvider( Shader_GLSL, null );

			_caps = ca;


			with( ligObjVec )
			{
				isStatic	= false;
				name		= "ligObj";
				num			= 0;
			}

			AddEnv( ligObjVec, sh_Vertex );

			with( ligObjEye )
			{
				isStatic	= false;
				name		= "eyeObj";
				num			= 1;
			}

			AddEnv( ligObjEye, sh_Vertex );

			with( ligEye )
			{
				isStatic	= false;
				name		= "eye";
				num			= 2;
			}

			AddEnv( ligEye, sh_Vertex );

			with( normLig0Pos )
			{
				isStatic	= false;
				name		= "ligPos0norm";
				num			= 0;
			}

			AddEnv( normLig0Pos, sh_Fragment );
		}


		~this()
		{
			delete _defProv;
		}
}
